/*
 * pcd8544_lcd.c
 *
 * Copyright 2022 dh33ex <dh33ex@riseup.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA or see <http://www.gnu.org/licenses/>.
 *
 *
 */

#ifndef __msp430_h_
    #include <msp430.h>
#endif
#include "pcd8544_lcd.h"


/* parameters */
/* H = 0 or 1 */
#define PCD8544_FUNCTIONSET     0x20
#define PCD8544_PD              0x04
#define PCD8544_V               0x02
#define PCD8544_H               0x01


/* H = 0 */
#define PCD8544_DISPLAYCONTROL  0x08
#define PCD8544_E               0x01
#define PCD8544_D               0x04

#define PCD8544_Y_ADDR          0x40
#define PCD8544_X_ADDR          0x80


/* H = 1 */
#define PCD8544_TEMPERATURECONTROL      0x4
#define PCD8544_TC0                     0x1
#define PCD8544_TC1                     0x2

#define PCD8544_BIASSYSTEM      0x10
#define PCD8544_BS0             0x1
#define PCD8544_BS1             0x2
#define PCD8544_BS2             0x4

#define PCD8544_VOP             0x80

/* D/C states */
#define PCD8544_COMMAND         0
#define PCD8544_DATA            1

static const char font[][5] = {
    {0x00, 0x00, 0x00, 0x00, 0x00},
    {0x00, 0x00, 0x5f, 0x00, 0x00},
    {0x00, 0x07, 0x00, 0x07, 0x00},
    {0x14, 0x7f, 0x14, 0x7f, 0x14},
    {0x24, 0x2a, 0x7f, 0x2a, 0x12},
    {0x23, 0x13, 0x08, 0x64, 0x62},
    {0x36, 0x49, 0x55, 0x22, 0x50},
    {0x00, 0x05, 0x03, 0x00, 0x00},
    {0x00, 0x1c, 0x22, 0x41, 0x00},
    {0x00, 0x41, 0x22, 0x1c, 0x00},
    {0x14, 0x08, 0x3e, 0x08, 0x14},
    {0x08, 0x08, 0x3e, 0x08, 0x08},
    {0x00, 0x50, 0x30, 0x00, 0x00},
    {0x08, 0x08, 0x08, 0x08, 0x08},
    {0x00, 0x60, 0x60, 0x00, 0x00},
    {0x20, 0x10, 0x08, 0x04, 0x02},
    {0x3e, 0x51, 0x49, 0x45, 0x3e},
    {0x00, 0x42, 0x7f, 0x40, 0x00},
    {0x42, 0x61, 0x51, 0x49, 0x46},
    {0x21, 0x41, 0x45, 0x4b, 0x31},
    {0x18, 0x14, 0x12, 0x7f, 0x10},
    {0x27, 0x45, 0x45, 0x45, 0x39},
    {0x3c, 0x4a, 0x49, 0x49, 0x30},
    {0x01, 0x71, 0x09, 0x05, 0x03},
    {0x36, 0x49, 0x49, 0x49, 0x36},
    {0x06, 0x49, 0x49, 0x29, 0x1e},
    {0x00, 0x36, 0x36, 0x00, 0x00},
    {0x00, 0x56, 0x36, 0x00, 0x00},
    {0x08, 0x14, 0x22, 0x41, 0x00},
    {0x14, 0x14, 0x14, 0x14, 0x14},
    {0x00, 0x41, 0x22, 0x14, 0x08},
    {0x02, 0x01, 0x51, 0x09, 0x06},
    {0x32, 0x49, 0x79, 0x41, 0x3e},
    {0x7e, 0x11, 0x11, 0x11, 0x7e},
    {0x7f, 0x49, 0x49, 0x49, 0x36},
    {0x3e, 0x41, 0x41, 0x41, 0x22},
    {0x7f, 0x41, 0x41, 0x22, 0x1c},
    {0x7f, 0x49, 0x49, 0x49, 0x41},
    {0x7f, 0x09, 0x09, 0x09, 0x01},
    {0x3e, 0x41, 0x49, 0x49, 0x7a},
    {0x7f, 0x08, 0x08, 0x08, 0x7f},
    {0x00, 0x41, 0x7f, 0x41, 0x00},
    {0x20, 0x40, 0x41, 0x3f, 0x01},
    {0x7f, 0x08, 0x14, 0x22, 0x41},
    {0x7f, 0x40, 0x40, 0x40, 0x40},
    {0x7f, 0x02, 0x0c, 0x02, 0x7f},
    {0x7f, 0x04, 0x08, 0x10, 0x7f},
    {0x3e, 0x41, 0x41, 0x41, 0x3e},
    {0x7f, 0x09, 0x09, 0x09, 0x06},
    {0x3e, 0x41, 0x51, 0x21, 0x5e},
    {0x7f, 0x09, 0x19, 0x29, 0x46},
    {0x46, 0x49, 0x49, 0x49, 0x31},
    {0x01, 0x01, 0x7f, 0x01, 0x01},
    {0x3f, 0x40, 0x40, 0x40, 0x3f},
    {0x1f, 0x20, 0x40, 0x20, 0x1f},
    {0x3f, 0x40, 0x38, 0x40, 0x3f},
    {0x63, 0x14, 0x08, 0x14, 0x63},
    {0x07, 0x08, 0x70, 0x08, 0x07},
    {0x61, 0x51, 0x49, 0x45, 0x43},
    {0x00, 0x7f, 0x41, 0x41, 0x00},
    {0x02, 0x04, 0x08, 0x10, 0x20},
    {0x00, 0x41, 0x41, 0x7f, 0x00},
    {0x04, 0x02, 0x01, 0x02, 0x04},
    {0x40, 0x40, 0x40, 0x40, 0x40},
    {0x00, 0x01, 0x02, 0x04, 0x00},
    {0x20, 0x54, 0x54, 0x54, 0x78},
    {0x7f, 0x48, 0x44, 0x44, 0x38},
    {0x38, 0x44, 0x44, 0x44, 0x20},
    {0x38, 0x44, 0x44, 0x48, 0x7f},
    {0x38, 0x54, 0x54, 0x54, 0x18},
    {0x08, 0x7e, 0x09, 0x01, 0x02},
    {0x0c, 0x52, 0x52, 0x52, 0x3e},
    {0x7f, 0x08, 0x04, 0x04, 0x78},
    {0x00, 0x44, 0x7d, 0x40, 0x00},
    {0x20, 0x40, 0x44, 0x3d, 0x00},
    {0x7f, 0x10, 0x28, 0x44, 0x00},
    {0x00, 0x41, 0x7f, 0x40, 0x00},
    {0x7c, 0x04, 0x18, 0x04, 0x78},
    {0x7c, 0x08, 0x04, 0x04, 0x78},
    {0x38, 0x44, 0x44, 0x44, 0x38},
    {0x7c, 0x14, 0x14, 0x14, 0x08},
    {0x08, 0x14, 0x14, 0x18, 0x7c},
    {0x7c, 0x08, 0x04, 0x04, 0x08},
    {0x48, 0x54, 0x54, 0x54, 0x20},
    {0x04, 0x3f, 0x44, 0x40, 0x20},
    {0x3c, 0x40, 0x40, 0x20, 0x7c},
    {0x1c, 0x20, 0x40, 0x20, 0x1c},
    {0x3c, 0x40, 0x30, 0x40, 0x3c},
    {0x44, 0x28, 0x10, 0x28, 0x44},
    {0x0c, 0x50, 0x50, 0x50, 0x3c},
    {0x44, 0x64, 0x54, 0x4c, 0x44},
    {0x00, 0x08, 0x36, 0x41, 0x00},
    {0x00, 0x00, 0x7f, 0x00, 0x00},
    {0x00, 0x41, 0x36, 0x08, 0x00},
    {0x10, 0x08, 0x08, 0x10, 0x08}
};

void PCD8544_send(PCD8544 *disp, unsigned char type, unsigned char byte) {
    while(UCB0STAT&UCBUSY);     /* wait until SPI is free */
    P2OUT &= ~disp->ss;         /* clear SS */
    if (type) {                 /* set/clear D/C pin */
        P2OUT |= disp->dc;
    } else {
        P2OUT &= ~disp->dc;
    }
    while(UCB0STAT&UCBUSY);
    UCB0TXBUF = byte;           /* send byte */
    while (UCB0STAT & UCBUSY);  /* wait until SPI free */
    __delay_cycles(3);          /* wait a little */
    P2OUT |= disp->ss;          /* set SS */
}


void LCD_init(PCD8544 *disp) {
    /* setup SPI */
    UCB0CTL1 |= UCSWRST;        /* put B0 into SW Reset */
    UCB0CTL1 |= UCSSEL_2;       /* choose SMCLK */
    UCB0BR0 = 1;                /* SMCLK=1M/1=1MHz */
    UCB0BR1 = 0;

    UCB0CTL0 |= UCSYNC          /* put B0 into SPI mode */
             | UCMSB            /* set MSB-first */
             | UCCKPH           /* set falling edge */
             | UCCKPL           /* set inactive state=HIGH */
             | UCMST;           /* set master mode */

    /* configure the ports */
    P1SEL |= BIT5;              /* P1.5 use CLK (11) */
    P1SEL2 |= BIT5;

    P1SEL |= BIT7;              /* P1.7 use SIMO (11) */
    P1SEL2 |= BIT7;

    P2DIR |= disp->bl | disp->dc | disp->ss | disp->rst;/* set ports to outputs */
    P2OUT &= ~disp->bl;                                 /* turn of backlight initially */
    P2OUT |= disp->dc | disp->ss | disp->rst;

    UCB0CTL1 &= ~UCSWRST;

    PCD8544_send(disp, PCD8544_COMMAND, PCD8544_FUNCTIONSET | PCD8544_H);
    PCD8544_send(disp, PCD8544_COMMAND, PCD8544_VOP | 0x3F);
    PCD8544_send(disp, PCD8544_COMMAND, PCD8544_TEMPERATURECONTROL | 0x02);
    PCD8544_send(disp, PCD8544_COMMAND, PCD8544_BIASSYSTEM | 0x03);
    PCD8544_send(disp, PCD8544_COMMAND, PCD8544_DISPLAYCONTROL | PCD8544_D);
    PCD8544_send(disp, PCD8544_COMMAND, PCD8544_FUNCTIONSET | PCD8544_V);
    LCD_clear(disp);
}


void LCD_write(PCD8544 *disp, char *string) {
    unsigned char i;

    PCD8544_send(disp, PCD8544_COMMAND, PCD8544_FUNCTIONSET);
    while (*string != '\0') {
        for(i = 0; i < 5; i++) {
            PCD8544_send(disp, PCD8544_DATA, font[*string - 0x20][i]);
        }
        string++;
        PCD8544_send(disp, PCD8544_DATA, 0);
    }
}


void LCD_backlight(PCD8544 *disp, unsigned char state) {
    if (state) {
        P2OUT |= disp->bl;
    } else {
        P2OUT &= ~disp->bl;
    }
}


void LCD_set_address(PCD8544 *disp, unsigned char x, unsigned char y) {
    PCD8544_send(disp, PCD8544_COMMAND, PCD8544_Y_ADDR | y);
    PCD8544_send(disp, PCD8544_COMMAND, PCD8544_X_ADDR | x);
}


void LCD_clear(PCD8544 *disp) {
    unsigned char j, i;
    for (i = 0; i < 84; i++) {
        for (j = 0; j < 6; j++) {
            PCD8544_send(disp, PCD8544_DATA,  0x0);
        }
    }
}
